Modules Should Be Deep
https://scrapbox.io/files/6580e935fabfbd0025c63e76.png
システムの複雑さ = 最も複雑なモジュールの複雑さ 複雑さを軽減するには、モジュール設計が重要となるradish-miyazaki.icon
他のモジュールについて知らなくても、どのモジュールでも作業できる
これはあくまで理想
実際問題、モジュールは互いのクラスやメソッドを呼び出す必要がある
結果として、互いのモジュールを知っている必要がある
これにより、あるモジュールが変更されると、他のモジュールも変更される
良いモジュール = インタフェースが実装よりもはるかにシンプル
これにより、以下の2つの恩恵を受けられる
シンプルなインタフェースは、モジュールがシステムの他の部分に与える複雑さを最小限にする
あるモジュールを(インタフェースを変更しないで)変更した場合、その変更は他のモジュールに影響を与えない
モジュールのインタフェースは2つの情報を持つ
プログラミング言語によって正しさがチェックされる部分
e.g. パラメータ名や型、戻り値の型、例外
プログラミング言語が理解したり、強制したりすることができない部分
e.g. 使い方に制約があるメソッド
あるメソッドが別のメソッドより先に呼び出す必要があるなど
プログラミング言語はこの記述が正確であるかを判断できない
formal information よりも大きく複雑
関連するモジュールを使用するために何を知る必要があるかを正確に示すことができる
各モジュールは、インタフェースという抽象化を提供する インタフェースは、モジュールの機能を簡略化したもの 実装の詳細は抽象化の観点から重要ではないので省かれている
省かれる重要ではない詳細は、多ければ多いほど良い
抽象化のよくある2つの間違い
重要でない詳細を含んでしまう
抽象化が複雑になり、抽象化を用いる開発者の認知的負荷が増大する 重要な詳細を含んでいない
抽象化を用いる開発者が、正しく使うために必要な情報を取得できない
抽象化を設計する上で重要なのは、何が重要なのかを理解し、重要な情報量を最小限に抑える設計を探すことである
e.g. ファイルシステム、電子レンジ、車
良いモジュール = 強力な機能を提供しながらもシンプルなインタフェースを持つ
https://scrapbox.io/files/6580e935fabfbd0025c63e76.png
シンプルなインタフェースの後ろに多くの機能が隠されている。
内部の複雑さの一部しかユーザに見せない
良い抽象化
深いモジュールはコスパ最高radish-miyazaki.icon モジュールにおける効果: 機能
モジュールにおける費用: インタフェース
インタフェースが小さくて単純であれば、もたらされる複雑さを軽減できる
e.g. Unix / Linux が提供する File I/O
open,read, write, lseek, close の5つのみ
提供する機能と比べて、インタフェースが複雑なモジュール
操作するのに必要なコードはそれほど多くないので、抽象化は詳細をあまり隠さない
インタフェースの複雑さ = 実装の複雑さ
code:java
private void addNullValueForAttribute(String attribute) {
data.put(attribute, null);
}
インタフェースを導入すると複雑さが増し、それによるメリットも少ない
コスパが悪い
避けられない場合もあるが、複雑さを管理する上では役立たない
N 行より長いメソッドは、複数のメソッドに分割すべき
Martin Fowler の リファクタリング と真逆のことを言ってるradish-miyazaki.icon 大きすぎるクラスもクラスで問題なので、真逆では無いradish-miyazaki.icon
Classitis に陥ったシステムは、個々のクラスはシンプルになるが、システム全体の複雑さが増す
小さなクラスはあまり機能に貢献しない
機能を実現するために大量のクラスが必要になる
それぞれが独自インタフェースを持つ
ボイラーテンプレートのために、冗長な記述になる
インタフェースは、一般的なケースをできるだけシンプルにするように実装されるべき
Java の ファイル I/O は違反している例
ほとんどのユーザはバッファリングを必要とするので、デフォルトで提供されるべき code:java
fileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
よく使われる機能のための API が、滅多に使われない他の機能についてユーザに学習を強いることで、認知的負荷 が高くなる 上記の例では、滅多に使われない他の機能 = バッファリングが必要ないケース
hr.icon
要約
ユーザはインタフェースによって提供される抽象化だけを理解すれば良い 一般的なユースケースを実現するためのシンプルなインタフェースを提供する
重要な機能を提供する